{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "def get_ret_and_cov(df, frequency):\n",
    "    \"\"\"输入：股票时间序列\n",
    "       输出：股票年画平均收益，股票收益的年化标准差，股票收益率的年化协方差矩阵。\n",
    "       frequency控制了数据频率：如果是日度数据，frequency=252；\n",
    "       如果是周度数据，frequency=52；如果是月度数据，frequency=12。本例中是月度数据\n",
    "    \"\"\"\n",
    "    ret_ave = pd.DataFrame(df.mean()).reset_index()\n",
    "    ret_ave.columns = ['Stkcd', 'ret_ave']\n",
    "    # 年化收益率\n",
    "    ret_ave['ret_ave'] = ret_ave['ret_ave'] * frequency\n",
    "    ret_std = pd.DataFrame(df.std()).reset_index()\n",
    "    ret_std.columns = ['Stkcd', 'ret_std']\n",
    "    ret_std['ret_std'] = ret_std['ret_std'] * np.sqrt(frequency)\n",
    "    # 年化协方差矩阵\n",
    "    ret_cov = df.cov() * frequency\n",
    "    return ret_ave, ret_std, ret_cov\n",
    "\n",
    "def mini_vol(number, ret_ave_vector, ret_cov_matrix, target_ret):\n",
    "    \"\"\"number是股票个数；ret_ave_vector是股票的收益率向量，注意是列向量；\n",
    "       ret_cov_matrix是股票收益率的协方差矩阵；\n",
    "       target_ret是目标收益率\n",
    "    \"\"\"\n",
    "    # 协方差矩阵的逆矩阵\n",
    "    ret_cov_matrix_I = ret_cov_matrix.I\n",
    "    # 全1向量e\n",
    "    e = np.matrix([1]*number).reshape((-1,1))\n",
    "    # 4*4的系数矩阵\n",
    "    a = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), ret_ave_vector))\n",
    "    b = float(np.dot(np.dot(e.T, ret_cov_matrix_I), ret_ave_vector))\n",
    "    c = float(np.dot(np.dot(e.T, ret_cov_matrix_I), e))\n",
    "    # 得到系数矩阵\n",
    "    coeff_matrix = np.matrix([a,b,b,c]).reshape((2,2))\n",
    "    coeff_matrix_I = coeff_matrix.I\n",
    "    # 解方程\n",
    "    y1 = np.matrix([target_ret, 1]).reshape((-1,1))\n",
    "    lambda_result = np.dot(coeff_matrix_I, y1)\n",
    "    lambda1 = float(lambda_result[0])\n",
    "    lambda2 = float(lambda_result[1])\n",
    "    # 得到权重\n",
    "    weights = lambda1 * np.dot(ret_cov_matrix_I, ret_ave_vector) + lambda2 * np.dot(\n",
    "                ret_cov_matrix_I, e)\n",
    "    return weights\n",
    "\n",
    "def port_info(weights, ret_ave_vector, ret_cov_matrix):\n",
    "    \"\"\"计算投资组合的年化收益率，以及年化波动率\"\"\"\n",
    "    port_return =  float(np.dot(weights.T, ret_ave_vector))\n",
    "    port_var = float(np.dot(np.dot(weights.T, ret_cov_matrix),weights))\n",
    "    port_std = np.sqrt(port_var)\n",
    "    return port_return, port_std\n",
    "\n",
    "\n",
    "\n",
    "def get_smallest_vol(ret_cov_matrix):\n",
    "    \"\"\"找出所有资产中最小的波动率；注意是返回σ，要开根号\"\"\"\n",
    "    diags = np.diag(ret_cov_matrix)\n",
    "    return np.sqrt(np.min(diags))\n",
    "\n",
    "def max_ret(number, ret_ave_vector, ret_cov_matrix, target_vol):\n",
    "    \"\"\"\n",
    "       number是股票个数；ret_ave_vector是股票的收益率向量，注意是列向量；\n",
    "       ret_cov_matrix是股票收益率的协方差矩阵；\n",
    "       target_vol是目标波动率，注意是σ。\n",
    "    \"\"\"\n",
    "    ret_cov_matrix_I = ret_cov_matrix.I\n",
    "    e = np.matrix([1]*number).reshape((-1,1))\n",
    "    \n",
    "    a = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), ret_ave_vector))\n",
    "    b = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), e))\n",
    "    c = float(np.dot(np.dot(e.T, ret_cov_matrix_I), e))\n",
    "    A = (target_vol * c) ** 2 - c\n",
    "    B = 2 * b * (1 - c * target_vol ** 2)\n",
    "    C = (target_vol * b) ** 2 - a\n",
    "    delta = B ** 2 - 4 * A * C\n",
    "    lambda21 = 0.5 / A * (- B + np.sqrt(delta))\n",
    "    lambda22 = 0.5 / A * (- B - np.sqrt(delta))\n",
    "    lambda11 =  (b - c * lambda21) / 2\n",
    "    lambda12 =  (b - c * lambda22) / 2\n",
    "    weights1 = np.dot(ret_cov_matrix_I, (ret_ave_vector - lambda21 * e))/(2*lambda11)\n",
    "    weights2 = np.dot(ret_cov_matrix_I, (ret_ave_vector - lambda22 * e))/(2*lambda12)\n",
    "    return weights1, weights2\n",
    "    \n",
    "\n",
    "monthly_data = pd.read_csv('dataset/return.csv', engine='python')\n",
    "ret_ave, ret_std, ret_cov = get_ret_and_cov(monthly_data, 12)\n",
    "# 生成收益率的列向量+协方差矩阵\n",
    "ret_ave1 = np.matrix(ret_ave['ret_ave']).reshape((-1,1))\n",
    "ret_cov1 = np.matrix(ret_cov)    \n",
    "# 计算权重：假设我的目标收益率是0\n",
    "weights = mini_vol(4, ret_ave1, ret_cov1, 0)\n",
    "# 计算投资组合的信息\n",
    "port_return, port_std = port_info(weights, ret_ave1, ret_cov1)\n",
    "\n",
    "# 首先判断所能够达到的最小波动率σ，下面穿进去的target_vol绝对不能够比它小：\n",
    "print(get_smallest_vol(ret_cov1))\n",
    "# 计算权重：假设我的目标波动率是40%\n",
    "weights1, weights2 = max_ret(4, ret_ave1, ret_cov1, 0.4)\n",
    "# 计算投资组合的信息\n",
    "port_return1, port_std1 = port_info(weights1, ret_ave1, ret_cov1)\n",
    "port_return2, port_std2 = port_info(weights2, ret_ave1, ret_cov1)\n",
    "\n",
    "\n",
    "port_ret = []\n",
    "port_std = []\n",
    "# 收益率从0到1，步长为0.00001，一共10万个收益率样本\n",
    "ranges = np.arange(0,1, 0.00001)\n",
    "for i in ranges:\n",
    "    weight = mini_vol(4, ret_ave1, ret_cov1, i)\n",
    "    port_ret1, port_std1 = port_info(weight, ret_ave1, ret_cov1)\n",
    "    port_ret.append(port_ret1)\n",
    "    port_std.append(port_std1)\n",
    "\n",
    "port_std = np.array(port_std)\n",
    "port_ret = np.array(port_ret)\n",
    "plt.figure(figsize = (16,9))\n",
    "plt.scatter(port_std,port_ret,c = port_ret/port_std, marker = 'o')\n",
    "plt.grid(True)\n",
    "plt.xlabel('excepted volatility')\n",
    "plt.ylabel('expected return')\n",
    "plt.colorbar(label = 'Sharpe ratio')   "
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
  },
  "kernelspec": {
   "display_name": "Python 3.7.3 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
